import requests
import json
from .cache import Cache
import types


class RemoteServer:
    def __init__(self, cache_size=200):
        self._cache = Cache(cache_size)
        self._id = 0

    def register(self, obj):
        return self._cache.put(obj)

    def remove(self, id):
        return self._cache.remove(id)

    def get_attr(self, id, attr):
        obj = self._cache.get(id)
        obj = getattr(obj, attr)
        return self.transform(obj)

    def call(self, id, func_name, args, kwargs):
        obj = self._cache.get(id)
        func = getattr(obj, func_name)
        return self.transform(func(*args, **kwargs))

    def transform(self, obj):
        if isinstance(obj, int) or isinstance(obj, float) or isinstance(obj, bool) or isinstance(obj, str):
            return obj
        if isinstance(obj, tuple) or isinstance(obj, list):
            return [self.transform(e) for e in obj]
        if isinstance(obj, dict):
            return {key: self.transform(value) for key, value in obj.items()}
        return {
            '__transformed__': True,
            'class': obj.__module__ + '.' + obj.__class__.__name__,
            'id': self.register(obj)
        }


class RemoteObject:
    def __init__(self, url, id, cls):
        self._url = url
        self._id = id
        self._funcs = {}
        for name in dir(cls):
            if not name.startswith('_'):
                attr = getattr(cls, name)
                if isinstance(attr, types.FunctionType):
                    attr = _MyFunc(self, attr.__name__)
                    self._funcs[name] = attr

    def __getattribute__(self, attr):
        if attr.startswith('_'):
            return super(RemoteObject, self).__getattribute__(attr)
        funcs = self._funcs
        if attr in funcs:
            return funcs[attr]
        return self._http_invoke('get_attr', {
            'id': self._id,
            'attr': attr
        })

    def _http_invoke(self, path, params):
        r = requests.post('%s/%s' % (self._url, path), data=params)
        if r.ok:
            result = json.loads(r.content)
            if result['err_code'] != 0:
                raise Exception(result['err_msg'])
        else:
            raise IOError(r.reason)

        result = result["result"]
        return self._untitransform(result)

    def _untitransform(self, obj):
        if isinstance(obj, int) or isinstance(obj, float) or isinstance(obj, bool) or isinstance(obj, str):
            return obj
        if isinstance(obj, tuple) or isinstance(obj, list):
            return [self._untitransform(e) for e in obj]
        if isinstance(obj, dict):
            if '__transformed__' in obj and obj['__transformed__']:
                return RemoteObject(self._url, obj['id'], obj['class'])
            return {key: self._untitransform(value) for key, value in obj.items()}
        raise Exception('Can not untitransform ' + str(obj))


class _MyFunc:
    def __init__(self, remote_object: RemoteObject, func_name):
        self.remote_object = remote_object
        self.func_name = func_name
        self.id = id

    def __call__(self, *args, **kwargs):
        return self.remote_object._http_invoke('call', {
            'id': self.id,
            'func_name': self.func_name,
            'args': args,
            'kwargs': kwargs
        })
